#include "biblioteca.h"
#include <sys/time.h>
#include <sys/types.h>

int training = 1;

//TODO: refazer mutação
void mutacao_local_troca(DP_INDI pop){
    void mutacao_peso(P_INDI indi, int i){
        int peso = random() % 10;
        while( peso == indi->atributos[i].peso )
            peso = random() % 10;
        indi->atributos[i].peso = peso;
    }
    void mutacao_operador(P_INDI indi, int i){
        int op = random() % NUM_OPERADORES;
        while( op == indi->atributos[i].operador )
            op = random() % NUM_OPERADORES;
        indi->atributos[i].operador = op;
    }
    void mutacao_valor(P_INDI indi, int i){
        int valor = ( i == SIZE_MATERIAL - 1 ) ? random() % 80 : ( i == 11 ) ? random() % 2 : random() % NUM_OPERADORES;
        while( valor == indi->atributos[i].valor )
            valor = ( i == SIZE_MATERIAL - 1 ) ? random() % 80 : ( i == 11 ) ? random() % 2 : random() % NUM_OPERADORES;
        indi->atributos[i].valor = valor;
    }
    //int max = ((TCROSS*SIZE_POP)/100);
    register int i = 0, j;
    for ( ; i < SIZE_POP; ++i){    
        for ( j = 0; j < SIZE_MATERIAL; ++j ){
            int sorteado = random() % 100;
            if ( sorteado < TMUT )
                mutacao_peso(pop[SIZE_POP + i], j);
            sorteado = random() % 100;
            if ( sorteado < TMUT )
                mutacao_operador(pop[SIZE_POP + i], j);
            sorteado = random() % 100;
            if ( sorteado < TMUT )
                mutacao_valor(pop[SIZE_POP + i], j);
        }
    }
}

//TODO: testar esse negocio de doido!
ATRIB *crossover_dois_pontos(P_INDI indi1, P_INDI indi2){
    ATRIB *retorno = calloc(1, 2 * SIZE_MATERIAL * sizeof(ATRIB));
    int ponto1 = random() % SIZE_MATERIAL;
    int ponto2 = random() % SIZE_MATERIAL;
    //testar se os números gerados não estão fora de ordem (ponto1 deve sempre ser menor que ponto2)
    while( ponto2 <= ponto1 ){
        ponto1 = random() % SIZE_MATERIAL;
        ponto2 = random() % SIZE_MATERIAL;
    }
    //copiar o material do pai1, intercalando o material do pai2
    //copiar o material do pai2, intercalando o material do pai1
    memcpy(retorno, indi1->atributos, ponto1 * sizeof(ATRIB));
    memcpy(retorno+(SIZE_MATERIAL), indi2->atributos, ponto1 * sizeof(ATRIB));

    memcpy(retorno+(ponto1), indi2->atributos+(ponto1), (ponto2 - ponto1) * sizeof(ATRIB));
    memcpy(retorno+(SIZE_MATERIAL+ponto1), indi1->atributos+(ponto1), (ponto2 - ponto1) * sizeof(ATRIB));
    
    memcpy(retorno+(ponto2), indi1->atributos+(ponto2), (SIZE_MATERIAL - ponto2) * sizeof(ATRIB));
    memcpy(retorno+(SIZE_MATERIAL+ponto2), indi2->atributos+(ponto2), (SIZE_MATERIAL - ponto2) * sizeof(ATRIB));
    //fim
    return retorno;
}

//TODO: fazer torneio e as outras coisas

    /*  
     *  Precisa receber uma população @pop e um ponteiro @pais do tipo DP_INDI com duas posições
     **/
void torneio(DP_INDI pop, DP_INDI pais){
    void um_pai_tour(DP_INDI pop, DP_INDI pai){
        int common_tour(DP_INDI pop){
            return random() % SIZE_POP;
        }
        int stochastic_tour(DP_INDI pop){
            int avaliacoes[SIZE_POP];
            register int i = 0, sum = 0;
            for ( ; i < SIZE_POP; ++i){
                sum += (pop[i]->evaluate * SIZE_POP);
                avaliacoes[i] = sum;
            }
            register int sorteado = random() % sum;
            for( i = 0; i < SIZE_POP; ++i){
                if ( sorteado <= avaliacoes[i] )
                    break;
            }
            return i;
        }
        DP_INDI temp[TOUR];
        register int i = 0;
        int *usados = calloc(SIZE_POP, sizeof(int));
        for( ; i < TOUR; ++i){
            int t;
            #ifndef ESTOCASTICO
                t = common_tour(pop);
                while(usados[t])
                    t = common_tour(pop);
            #else
                t = stochastic_tour(pop);
	        while(usados[t])
                    t = stochastic_tour(pop);
	    #endif
            usados[t] = 1;
            memcpy(temp+i, pop+t, sizeof(P_INDI));
        }
        qsort(temp, TOUR, sizeof(P_INDI), compara_avaliacao_individuos_const_void);
        memcpy(pai, temp, sizeof(P_INDI));
        free(usados);
    }
    um_pai_tour(pop, pais);
    um_pai_tour(pop, pais+1);
}

void executa_algoritmo_genetico(DP_INDI populacao, void(*seleciona_pais)(DP_INDI pop, DP_INDI pais), ATRIB *(*crossover)(P_INDI indi1, P_INDI indi2),
                                int (*compara_individuos)(const void* indi1, const void* indi2), void (*avaliador)(P_INDI indi), void (*mutacao)(DP_INDI indi)){

    //struct timeval tv1, tv2;
    //gettimeofday(&tv1, NULL);

    register int execucao_atual = 0, geracao_atual, i;//, solucao_found;
    int tamanho_extra = ((TCROSS*SIZE_POP)/100);
    int tamanho_total = SIZE_POP + tamanho_extra;

    printf("Tamanho extra de %d\n",tamanho_extra);
    for( ; execucao_atual < EXECUCOES; ++execucao_atual){
        //printf("\tExecução atual: %d\n",execucao_atual + 1);
        training = 1;
        //solucao_found = 0;
        DP_INDI pop = calloc(tamanho_total, sizeof(P_INDI));
        //alocou nova populacao copiando os pais
        for( i = 0; i < SIZE_POP; ++i){
            pop[i] = calloc(1, sizeof(INDI));
            if ( i < SIZE_POP ){
                memcpy(pop+i, populacao+i, sizeof(INDI));
            }
        }
        //até aqui, a populacao tem tamanho SIZE_POP!
        qsort(pop, SIZE_POP, sizeof(P_INDI), compara_individuos);
        for( geracao_atual = 0; geracao_atual < NUM_GERACOES; ++geracao_atual){
            for( i = 0; i < SIZE_POP; ++i){
                avaliador(pop[i]);
            }
//	    qsort(pop, SIZE_POP, sizeof(P_INDI), compara_individuos);
            //gerar filhos
            for( i = 0; i < tamanho_extra; i += 2){
                //seleciona dois pais
                P_INDI pais[2];
                seleciona_pais(pop, pais);
                //cria dois filhos
                ATRIB *filhos = crossover(pais[0], pais[1]);
                INDI i1 = create_individuo_com_material(filhos);
                INDI i2 = create_individuo_com_material(filhos+SIZE_MATERIAL);
                //copia eles para a populacao
                pop[SIZE_POP + i] = copiar_individuo(&i1);
                pop[SIZE_POP + i + 1] = copiar_individuo(&i2);
            }
            mutacao(pop);
            for( i = 0; i < tamanho_total; ++i){
                avaliador(pop[i]);
	    }
            #ifdef ELITISMO
                //ordena a população de pais e preserva a elite, copiar os filhos sobre o restante dos pais
                qsort(pop, tamanho_extra, sizeof(P_INDI), compara_individuos);
                //qsort(pop, SIZE_POP, sizeof(P_INDI), compara_individuos);
                //copy_population_to_other(pop+(1), pop+(SIZE_POP), tamanho_extra - 1);
            #else
                //ordena a população
                qsort(pop, tamanho_total, sizeof(P_INDI), compara_individuos);
                //copy_population_to_other(pop_nova, pop, calculo);
            #endif
        }
        //printf("Melhor indivíduo da execução:\n\t");
        training = 1;
        ///*for ( i = 0; i < 1; ++i){
            avaliador(pop[0]);
            //printf("\tAvaliação: %15.14f\n",pop[i]->evaluate);
            imprime_individuo(pop[0]);
        //}*/
        //int integer;
        //scanf(" %d\n",&integer);
        //imprime_individuo(pop[0]);
        //printf("%f\n",pop[0]->evaluate);
        //free(pop);
    }
    //gettimeofday(&tv2, NULL);
    //printf("%f\n",(double)(tv1.tv_usec-tv2.tv_usec));
//    printf("Solucao encontrada %d vezes em %d execucoes. (Convergencia de %.5f)\n",vezes_encontrada, EXECUCOES, (float)vezes_encontrada/EXECUCOES);
//    printf("EXPERIMENTO: %3d\tSIZEPOP: %3d\tPCROSS: %3d\tPMUT: %3d\t TOUR: %3d\t NRO GERACOES: %3d\n",EXPERIMENTO, SIZE_POP, TCROSS, TMUT, TOUR, NUM_GERACOES);
}

